package com.ilearn.iodata.iolingo.service.processor;

import com.ilearn.iodata.iolingo.edm.core.FilterInputParam;
import com.ilearn.iodata.iolingo.service.GetCollectionService;
import com.ilearn.iodata.iolingo.service.GetEntityService;
import com.ilearn.iodata.iolingo.utils.OlingoUtil;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.processor.Processor;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.serializer.SerializerResult;
import org.apache.olingo.server.api.uri.*;
import org.apache.olingo.server.api.uri.queryoption.FilterOption;
import org.apache.olingo.server.api.uri.queryoption.OrderByItem;
import org.apache.olingo.server.api.uri.queryoption.OrderByOption;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.core.uri.parser.ExpressionParser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.parser.UriTokenizer;
import org.apache.olingo.server.core.uri.validator.UriValidationException;

import java.io.InputStream;
import java.util.List;
import java.util.Locale;

import static com.ilearn.iodata.iolingo.constant.OdataConst.firstAlias;
import static com.ilearn.iodata.iolingo.utils.OlingoUtil.getSelectClause;
import static com.ilearn.iodata.iolingo.utils.OlingoUtil.getStructuralProperty;

/**
 * Created by lyndon on 2018/5/31.
 */
public class GenericODataBaseProcessor implements Processor {

    protected OData odata;
    protected ServiceMetadata serviceMetadata;

    @Override
    public void init(OData odata, ServiceMetadata serviceMetadata) {
        this.odata = odata;
        this.serviceMetadata = serviceMetadata;
    }

    UriResourceEntitySet getUriResourceEntitySetFromUriResources(int index, List<UriResource> resources)
            throws ODataApplicationException {
        UriResource uriResource = resources.get(index);
        if (!(uriResource instanceof UriResourceEntitySet)) {
            throw new ODataApplicationException(
                    uriResource.getSegmentValue() + " is not an EntitySet, which may be used here",
                    HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
        }
        return (UriResourceEntitySet) uriResource;
    }



    /**
     * 将序列化值传入ODataResponse,并设定响应 ContentType
     *
     * @param response
     * @param responseFormat
     * @param serializerResult
     */
    void populateResponseForFormat(ODataResponse response, ContentType responseFormat,
                                   SerializerResult serializerResult) {
        populateResponseForFormat(response, responseFormat.toContentTypeString(), serializerResult.getContent());
    }

    /**
     * 将InputStream传入ODataResponse,并设定响应 ContentType
     *
     * @param response
     * @param format
     * @param result
     */
    void populateResponseForFormat(ODataResponse response, String format, InputStream result) {
        response.setContent(result);
        response.setStatusCode(HttpStatusCode.OK.getStatusCode());
        response.setHeader(HttpHeader.CONTENT_TYPE, format);
    }


    void assertNotNull(Entity entity) throws ODataApplicationException {
        if (entity == null) {
            throw new ODataApplicationException("Nothing found", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT);
        }
    }

    void throwNotImplemented(String actionName) throws ODataApplicationException {
        throw new ODataApplicationException(actionName + " is not supported",
                HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
    }

    void throwTooManySegments() throws ODataApplicationException {
        throw new ODataApplicationException("Too many navigation properties in URL (maximum 2 entities allowed)",
                HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
    }

    /**
     * 拼 orderby 子句
     *
     * @param uri
     * @return
     */
    public String getOrderByStr(UriInfo uri) {
        //http://localhost:8081/chinabond.svc/members?$select=first_name,last_name&$orderby=first_name%20asc,last_name%20desc
        StringBuilder orderbyStr = new StringBuilder();
        OrderByOption orderByOption = uri.getOrderByOption();
        if (orderByOption != null) {
            List<OrderByItem> orders = orderByOption.getOrders();
            int i = orders.size();
            String comma = ",";
            for (OrderByItem orderByItem : orders) {
                comma = (i == 1) ? "" : ",";
                Expression expression = orderByItem.getExpression();

                if (expression instanceof Member) {
                    UriInfoResource resourcePath = ((Member) expression).getResourcePath();
                    UriResource uriResource = resourcePath.getUriResourceParts().get(0);
                    if (uriResource instanceof UriResourcePrimitiveProperty) {
                        EdmProperty edmProperty = ((UriResourcePrimitiveProperty) uriResource).getProperty();
                        final String sortPropertyName = edmProperty.getName();
                        orderbyStr.append(sortPropertyName).append(" ").append(orderByItem.isDescending() ? "desc" : "asc").append(comma);
                    }
                }
                i--;
            }
        }
        System.out.println("order by >>>>>" + orderbyStr.toString());
        return orderbyStr.toString();
    }

    /**
     * 拼 where 子句
     *
     * @param uri
     * @param edmEntitySet
     * @return
     * @throws ODataApplicationException
     * @throws ExpressionVisitException
     */
    public String getFilter2WhereClause(UriInfo uri, EdmEntitySet edmEntitySet) throws ODataApplicationException, ExpressionVisitException {
        FilterOption filterOption = uri.getFilterOption();
        String filterStr = "";
        if (filterOption != null) {
            // Apply $filter system query option
            Expression filterExpression = filterOption.getExpression();
            System.out.println("VisitPrimitiveExpression:" + filterExpression);
            FilterExpressionVisitor fev = new FilterExpressionVisitor().in(edmEntitySet);

            filterStr = filterExpression.accept(fev);

            System.out.println("filterStr:" + filterStr);

            System.out.println("fs:" + fev.getFilterInputParams());

            for (FilterInputParam filterInputParam : fev.getFilterInputParams()) {
                System.out.println("f:" + filterInputParam);
            }

            /*FilterExpressionVisitor filterExpressionVisitor = new FilterExpressionVisitor();
            filterExpressionVisitor.in(edmEntitySet);
            String hql = filterExpressionVisitor.prepareHQL(filterExpression.toString());
            System.out.println("hql:" + hql);*/

        }
        return filterStr;
    }


    public String genericSQL(OData odata, UriInfo uriInfo, ServiceMetadata serviceMetadata, EdmEntitySet edmEntitySet) {

        String sql = "";

        List<EdmProperty> edmProperties = getStructuralProperty(serviceMetadata, edmEntitySet);

        SelectOption selectOption = uriInfo.getSelectOption();
        selectOption.getText();

        try {
            String selectClause = getSelectClause(odata, uriInfo, edmEntitySet.getEntityType());
        } catch (SerializerException e) {
            e.printStackTrace();
        }

        return sql;
    }


    /**
     * 拼一般select SQL
     *
     * @param selectClause
     * @param whereClause
     * @param entitySet
     * @param orderbyClause
     * @return
     */
    public String prepareHQL(String selectClause, String whereClause, EdmEntitySet entitySet, String orderbyClause) {

        String select = "*";
        if (selectClause == null || "".equals(selectClause)) {
        } else {
            select = OlingoUtil.splitAroundLink(selectClause, ",", firstAlias + ".", "", ",");
        }

        StringBuilder hql = new StringBuilder("SELECT ")
                .append(select)
                .append(" FROM ")
                .append(entitySet.getName()).append(" ").append(firstAlias);

        entitySet.getEntityType().getPropertyNames();
        entitySet.getEntityType().getNavigationPropertyNames();


        // there is a join operation needed
        /*if (filterHQL.contains(secondAlias)) {
            hql.append(" LEFT JOIN ").append(firstAlias).append(".").append(usedNavProp).append(" ").append(secondAlias);
        }*/

        /* 可以考虑多表实体主外键关联
        List<EdmEntitySet> entitySets = serviceMetadata.getEdm().getEntityContainer().getEntitySets();
        System.out.println("entitySets" + entitySets);
        EdmEntityType membersType = serviceMetadata.getEdm().getEntityType(new FullQualifiedName(NAMESPACE+ "." + "members"));
        List<String> propertyNames = membersType.getPropertyNames();
        System.out.println("members metadata:");
        propertyNames.forEach(e -> System.out.println(e));
        System.out.println("=================");*/

        StringBuilder where = new StringBuilder();

        if (whereClause == null || "".equals(whereClause)) {
            where.append(" ");
        } else {
            where.append(" WHERE ").append(whereClause);
        }

        if (orderbyClause == null || "".equals(orderbyClause)) {
            orderbyClause = "";
        } else {
            orderbyClause = " order by " + orderbyClause;
        }

        StringBuilder sql = hql.append(where).append(orderbyClause);

        return sql.toString();
    }

    /**
     * 拼countSQL
     *
     * @param selectClause
     * @param whereClause
     * @param entitySet
     * @return
     */
    public String prepareCountHQL(String selectClause, String whereClause, EdmEntitySet entitySet) {

        String select = "count(1)";

        StringBuilder hql = new StringBuilder("SELECT ")
                .append(select)
                .append(" FROM ")
                .append(entitySet.getName()).append(" ").append(firstAlias);

        StringBuilder where = new StringBuilder();

        if (whereClause == null || "".equals(whereClause)) {
            where.append("");
        } else {
            where.append(" WHERE ").append(whereClause);
        }

        StringBuilder sql = hql.append(where);

        return sql.toString();
    }


    private Expression parseExpression(final String expressionString)
            throws UriParserException, UriValidationException {
        UriTokenizer tokenizer = new UriTokenizer(expressionString);
        final Expression expression = new ExpressionParser(serviceMetadata.getEdm(), odata).parse(tokenizer, null, null, null);
        //assertNotNull(expression);
        //assertTrue(tokenizer.next(UriTokenizer.TokenKind.EOF));
        return expression;
    }

    /**
     * 获取查询记录
     *
     * @param getCollectionService
     * @param edmEntitySet
     * @param sqlStatement
     * @return
     */
    public EntityCollection getCollection(GetCollectionService getCollectionService, EdmEntitySet edmEntitySet, String sqlStatement, EntityCollection entityCollection) {

        //EntityCollection entityCollection = new EntityCollection();

        List<Entity> entities = entityCollection.getEntities();
        String entitySetName = edmEntitySet.getName();
        if (sqlStatement == null || "".equals(sqlStatement)) {

            StringBuilder sql = new StringBuilder();
            sql.append("select * from ");



        /*List<UriResource> resourcePaths = uriInfo.getUriResourceParts();
        UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths
                .get(0); // in our example, the first segment is the EntitySet
        EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();*/

            sql.append(entitySetName);

            sqlStatement = sql.toString();
        }
        System.out.println(">>>>>>>>sql>>>>>>>>> " + sqlStatement + " <<<<<<<<<<<<<<<<<<");

        entities.addAll(getCollectionService.getCollectionByJdbcTemplate(sqlStatement, entitySetName, edmEntitySet.getEntityType().getKeyPropertyRefs()));

        return entityCollection;
    }


    /**
     * $count=true,获取count值.
     *
     * @param getCollectionService
     * @param sqlStatement
     * @return
     */
    public Integer getCollectionCount(GetCollectionService getCollectionService, String sqlStatement) {

        System.out.println(">>>>>>>>count sql>>>>>>>>> " + sqlStatement + " <<<<<<<<<<<<<<<<<<");

        return getCollectionService.getCollectionCountByJdbcTemplate(sqlStatement);
    }


    private Entity getEntityByKey(GetEntityService getEntityService, EdmEntitySet edmEntitySet, List<UriParameter> keyPredicates) {

        List<EdmKeyPropertyRef> keyPropertyRefs = edmEntitySet.getEntityType().getKeyPropertyRefs();


        String entitySetName = edmEntitySet.getName();

        StringBuilder sql = new StringBuilder();
        sql.append("select * from ");
        sql.append(entitySetName);
        sql.append(" where 1=1");

        for (UriParameter keyPredicate : keyPredicates) {
            EdmKeyPropertyRef keyPropertyRef = edmEntitySet.getEntityType().getKeyPropertyRef(keyPredicate.getName());

            /*String value = keyPredicate.getText();
            if (keyPropertyRef.getProperty().getType().getKind().equals(EdmTypeKind.PRIMITIVE)) {
                if (keyPropertyRef.getProperty().getType().getFullQualifiedName().equals(EdmPrimitiveTypeKind.String.getFullQualifiedName())) {
                    value = OlingoUtil.mkString(value, "'");
                }
            }*/
            keyPropertyRef.getProperty().getType().getKind();
            sql.append(" and " + keyPredicate.getName() + "=" + keyPredicate.getText()); // + keyPredicate.getText()
        }
        String sqlStatement = sql.toString();
        System.out.println("》》》》》》》》》 " + sqlStatement + " 《《《《《《《《《《《《《《《");
        Entity entity = getEntityService.getEntityByJdbcTemplate(sqlStatement, entitySetName, keyPropertyRefs);
        return entity;
    }

    /**
     * 括号传参调用
     *
     * @param getCollectionService
     * @param edmEntitySet
     * @param keyPredicates
     * @return
     */
    public EntityCollection getCollectionByKey(GetCollectionService getCollectionService, EdmEntitySet edmEntitySet, List<UriParameter> keyPredicates) {

        List<EdmKeyPropertyRef> keyPropertyRefs = edmEntitySet.getEntityType().getKeyPropertyRefs();

        EntityCollection entityCollection = new EntityCollection();

        List<Entity> entities = entityCollection.getEntities();


        String entitySetName = edmEntitySet.getName();

        StringBuilder sql = new StringBuilder();
        sql.append("select * from ");
        sql.append(entitySetName);
        sql.append(" where 1=1");

        /*
            处理入参
         */
        for (UriParameter keyPredicate : keyPredicates) {

            String keyPredicateVal = keyPredicate.getText();
            final EdmKeyPropertyRef keyPropertyRef = edmEntitySet.getEntityType().getKeyPropertyRef(keyPredicate.getName());
            if (keyPropertyRef.getProperty().getType().getFullQualifiedName().equals(EdmPrimitiveTypeKind.Date.getFullQualifiedName())) {
                keyPredicateVal = OlingoUtil.mkString(keyPredicateVal, "'");
            }

            System.out.println("加工后：" + keyPredicateVal);

            sql.append(" and " + keyPredicate.getName() + "=" + keyPredicateVal); // + keyPredicate.getText()
        }

        String sqlStatement = sql.toString();
        System.out.println("》》》》》》》》》 " + sqlStatement + " 《《《《《《《《《《《《《《《");
        entities.addAll(getCollectionService.getCollectionByJdbcTemplate(sqlStatement, entitySetName, keyPropertyRefs));
        return entityCollection;
    }

    public OData getOdata() {
        return odata;
    }

    public ServiceMetadata getServiceMetadata() {
        return serviceMetadata;
    }
}
